from zope.interface import implements

from twisted.application import internet
from twisted.plugin import IPlugin
from twisted.internet import protocol, reactor, defer, ssl
from twisted.protocols import basic
from twisted.python import components
from pyhsrv import interfaces, errors

VERBOSE = True

class PyHMonProtocol(basic.NetstringReceiver):

    def writeValue(self, value):
        if value != None:
            if VERBOSE:
                print value
            self.sendString(str(value))

    def stringReceived(self, msg):
        if VERBOSE:
            print msg
        d = self.factory.parseMsg(msg)
        d.addErrback(errors.catchError)
        d.addCallback(self.writeValue)

    def connectionMade(self):
        d = self.factory.newConnection(self.writeValue)
        d.addErrback(errors.catchError)
        d.addCallback(self.writeValue)
    
    def connectionLost(self, reason):
        if VERBOSE:
            print reason


class NetworkFactoryFromService(protocol.ReconnectingClientFactory):

    implements(interfaces.INetworkFactory)

    protocol = PyHMonProtocol

    def __init__(self, service):
        self.service = service
        self.reconn_counter = 0
        self.maxDelay = 300
        self.initialDelay = 10

    def parseMsg(self, msg):
        return self.service.parseMsg(msg)

    def newConnection(self, sendfv):
        self.reconn_counter = 0
        self.service.updateStatus("online")
        return self.service.newConnection(sendfv)

    def closeConnection(self, p_inst):
        return self.service.closeConnection(p_inst)
    
    def clientConnectionLost(self, connector, reason):
        protocol.ReconnectingClientFactory.clientConnectionLost(self,
                                                                connector,
                                                                reason)

    def clientConnectionFailed(self, connector, reason):
        self.reconn_counter += 1
        if self.reconn_counter < 5:
            protocol.ReconnectingClientFactory.clientConnectionFailed(self,
                                                                      connector,
                                                                      reason)
        else:
            self.service.updateStatus("unreachable")
            d = self.service.getAddr()
            def setaddr(addr_and_port):
                connector.host = addr_and_port[0]
                connector.port = addr_and_port[1]
                self.reconn_counter = 0
                protocol.ReconnectingClientFactory.clientConnectionFailed(self,
                                                            connector, reason)
            d.addCallback(setaddr)
            d.addErrback(errors.catchError)
    
class TCPPlugin(object):

    implements(IPlugin, interfaces.INetPlugin)

    def __init__(self):
        self.name = "tcp"

    def getName(self):
        return self.name

    def makeService(self, parser, ip_and_port, isssl, myKey, trustedCA):
        addr = ip_and_port[0]
        port = ip_and_port[1]
        if isssl:
            # read the certificate files:
            fd = open(myKey,'r')
            theCert = ssl.PrivateCertificate.loadPEM(fd.read())
            fd.close()
            fd = open(trustedCA,'r')
            theCA = ssl.Certificate.loadPEM(fd.read())
            fd.close()
            ctx = theCert.options(theCA)
            
            # set the SSL version to v3:
            ctx.method = ssl.SSL.SSLv3_METHOD
            
            # If True, verify certificates received from the peer and fail
            # the handshake if verification fails. Otherwise, allow anonymous
            # sessions and sessions with certificates which fail validation.
            ctx.verify = True

            # Depth in certificate chain down to which to verify.
            ctx.verifyDepth = 1
       
            # If True, do not allow anonymous sessions.
            ctx.requireCertification = True
       
            # If True, do not re-verify the certificate on session resumption.
            ctx.verifyOnce = True
       
            # If True, generate a new key whenever ephemeral DH parameters are
            # used to prevent small subgroup attacks.
            ctx.enableSingleUseKeys = True
       
            # If True, set a session ID on each context. This allows a shortened
            # handshake to be used when a known client reconnects.
            ctx.enableSessions = True
       
            # If True, enable various non-spec protocol fixes for broken
            # SSL implementations.
            ctx.fixBrokenPeers = False
            s = internet.SSLClient(addr,
                                   port,
                                   interfaces.INetworkFactory(parser),
                                   ctx)
        else:
            s = internet.TCPClient(addr,
                                   port,
                                   interfaces.INetworkFactory(parser))
        return s

components.registerAdapter(NetworkFactoryFromService,
            interfaces.IPollerService,
            interfaces.INetworkFactory)    
    
tp = TCPPlugin()

class PyHSrvProtocol(basic.NetstringReceiver):

    def __init__(self):
        pass

    def writeValue(self, value):
        if value != None:
            if VERBOSE:
                print value
            self.sendString(str(value))

    def stringReceived(self, msg):
        if VERBOSE:
            print msg
        d = self.factory.parseMsg(msg, self)
        d.addErrback(errors.catchError)
        d.addCallback(self.writeValue)

    def connectionMade(self):
        self.factory.newConnection(self, self.transport.getPeer().host)

    def connectionLost(self, reason):
        if VERBOSE:
            print reason
        return self.factory.closeConnection(self)

class FakeNodeFactoryFromService(protocol.ServerFactory):

    implements(interfaces.INetworkFactory)

    protocol = PyHSrvProtocol

    def __init__(self, service):
        self.service = service

    def parseMsg(self, msg, p_inst):
        return self.service.parseMsg(msg, p_inst)

    def newConnection(self, p_inst, ipaddr):
        return self.service.newConnection(p_inst, ipaddr)

    def closeConnection(self, p_inst):
        return self.service.closeConnection(p_inst)


class FakeNodePlugin(object):
    
    implements(IPlugin, interfaces.IFakeNodeNetPlugin)
    
    def __init__(self):
        self.name = "pyhmon"
    
    def getName(self):
        return self.name
    
    def makeService(self, parser, conf):
        try:
            sslenabled = conf.get(self.name, 'ssl') in ["yes", "true"]
        except:
            sslenabled = False
        if sslenabled:
            self.port = conf.getint(self.name, 'port')
            # read the certificate files:
            myKey = conf.get('general', 'mysslcert')
            trustedCA = conf.get('general', 'casslcert')
            fd = open(myKey,'r')
            theCert = ssl.PrivateCertificate.loadPEM(fd.read())
            fd.close()
            fd = open(trustedCA,'r')
            theCA = ssl.Certificate.loadPEM(fd.read())
            fd.close()
            ctx = theCert.options(theCA)
            
            # set the SSL version to v3:
            ctx.method = ssl.SSL.SSLv3_METHOD
            
            # If True, verify certificates received from the peer and fail
            # the handshake if verification fails. Otherwise, allow anonymous
            # sessions and sessions with certificates which fail validation.
            ctx.verify = True

            # Depth in certificate chain down to which to verify.
            ctx.verifyDepth = 1
       
            # If True, do not allow anonymous sessions.
            ctx.requireCertification = True
       
            # If True, do not re-verify the certificate on session resumption.
            ctx.verifyOnce = True
       
            # If True, generate a new key whenever ephemeral DH parameters are
            # used to prevent small subgroup attacks.
            ctx.enableSingleUseKeys = True
       
            # If True, set a session ID on each context. This allows a shortened
            # handshake to be used when a known client reconnects.
            ctx.enableSessions = True
       
            # If True, enable various non-spec protocol fixes for broken
            # SSL implementations.
            ctx.fixBrokenPeers = False
            s = internet.SSLServer(self.port,
                                   interfaces.INetworkFactory(parser), ctx)
        else:
            self.port = conf.getint(self.name, 'port')
            s = internet.TCPServer(self.port,
                                   interfaces.INetworkFactory(parser))
        return s

components.registerAdapter(FakeNodeFactoryFromService,
            interfaces.IFakeNodeService,
            interfaces.INetworkFactory)
    
fnp = FakeNodePlugin()

class AlertFactoryFromService(protocol.ServerFactory):

    implements(interfaces.INetworkFactory)

    protocol = PyHSrvProtocol

    def __init__(self, service):
        self.service = service

    def parseMsg(self, msg, p_inst):
        return self.service.parseMsg(msg, p_inst)

    def newConnection(self, p_inst, ipaddr):
        return self.service.newConnection(p_inst, ipaddr)

    def closeConnection(self, p_inst):
        return self.service.closeConnection(p_inst)


class AlertPlugin(object):

    implements(IPlugin, interfaces.INetAlertPlugin)

    def __init__(self):
        self.name = "tcp"
    
    def makeService(self, ctrl, cfg):
        if cfg.getboolean(self.name, "ssl"):
            port = cfg.getint(self.name, "port")
            # read the certificate files:
            myKey = cfg.get('general', 'mysslcert')
            trustedCA = cfg.get('general', 'casslcert')
            fd = open(myKey,'r')
            theCert = ssl.PrivateCertificate.loadPEM(fd.read())
            fd.close()
            fd = open(trustedCA,'r')
            theCA = ssl.Certificate.loadPEM(fd.read())
            fd.close()
            ctx = theCert.options(theCA)
            
            # set the SSL version to v3:
            ctx.method = ssl.SSL.SSLv3_METHOD
            
            # If True, verify certificates received from the peer and fail
            # the handshake if verification fails. Otherwise, allow anonymous
            # sessions and sessions with certificates which fail validation.
            ctx.verify = True

            # Depth in certificate chain down to which to verify.
            ctx.verifyDepth = 1
       
            # If True, do not allow anonymous sessions.
            ctx.requireCertification = True
       
            # If True, do not re-verify the certificate on session resumption.
            ctx.verifyOnce = True
       
            # If True, generate a new key whenever ephemeral DH parameters are
            # used to prevent small subgroup attacks.
            ctx.enableSingleUseKeys = True
       
            # If True, set a session ID on each context. This allows a shortened
            # handshake to be used when a known client reconnects.
            ctx.enableSessions = True
       
            # If True, enable various non-spec protocol fixes for broken
            # SSL implementations.
            ctx.fixBrokenPeers = False
            s = internet.SSLServer(port, interfaces.INetworkFactory(ctrl), ctx)
        else:
            port = cfg.getint(self.name, "port")
            s = internet.TCPServer(port, interfaces.INetworkFactory(ctrl))
        return s
    
    def getName(self):
        return self.name

components.registerAdapter(AlertFactoryFromService,
            interfaces.IControllerService,
            interfaces.INetworkFactory)

ap = AlertPlugin()
